﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace SJ.Platform.Common
{
    public static class EnumerableExtensions
    {
        /// <summary>
        /// 实体列表转换成实体对应的扩展列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <param name="selector"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static List<TResult> MapToList<T, TResult>(this IEnumerable<T> source)
        {
            try
            {
                if (source == null) return null;

                var tProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
                var tResultProperties = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance);

                var propertyMap = tProperties
                    .Join(tResultProperties,
                        srcProp => srcProp.Name,
                        destProp => destProp.Name,
                        (srcProp, destProp) => new { SourceProperty = srcProp, DestinationProperty = destProp })
                    .Where(pair => pair.SourceProperty.PropertyType.IsAssignableFrom(pair.DestinationProperty.PropertyType) ||
                                     pair.DestinationProperty.PropertyType.IsAssignableFrom(pair.SourceProperty.PropertyType))
                    .ToDictionary(pair => pair.SourceProperty.Name, pair => pair.DestinationProperty);

                List<TResult> results = new List<TResult>();

                foreach (var item in source)
                {
                    TResult result = Activator.CreateInstance<TResult>();

                    foreach (var prop in tProperties)
                    {
                        if (propertyMap.TryGetValue(prop.Name, out PropertyInfo destProp))
                        {
                            try
                            {
                                destProp.SetValue(result, prop.GetValue(item, null), null);
                            }
                            catch (Exception ex)
                            {
                                return null;
                            }
                        }
                    }

                    results.Add(result);
                }

                return results;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        /// <summary>
        /// 实体转成扩展对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="source"></param>
        /// <returns></returns>
        public static TResult MapToExt<T, TResult>(this T source)
        {
            try
            {
                if (source == null) return default(TResult);

                var tProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
                var tResultProperties = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance);

                var propertyMap = tProperties
                    .Join(tResultProperties,
                        srcProp => srcProp.Name,
                        destProp => destProp.Name,
                        (srcProp, destProp) => new { SourceProperty = srcProp, DestinationProperty = destProp })
                    .Where(pair => pair.SourceProperty.PropertyType.IsAssignableFrom(pair.DestinationProperty.PropertyType) ||
                                     pair.DestinationProperty.PropertyType.IsAssignableFrom(pair.SourceProperty.PropertyType))
                    .ToDictionary(pair => pair.SourceProperty.Name, pair => pair.DestinationProperty);

                TResult result = Activator.CreateInstance<TResult>();
                foreach (var prop in tProperties)
                {
                    if (propertyMap.TryGetValue(prop.Name, out PropertyInfo destProp))
                    {
                        try
                        {
                            destProp.SetValue(result, prop.GetValue(source, null), null);
                        }
                        catch (Exception ex)
                        {
                            return default(TResult);
                        }
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                return default(TResult);
            }
        }

        /// <summary>
        /// 从扩展中提取实体
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="ext"></param>
        /// <returns></returns>
        public static T ExtMapToEntity<T, TResult>(this TResult ext)
        {
            try
            {
                if (ext == null) return default(T);

                var tProperties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
                var tResultProperties = typeof(TResult).GetProperties(BindingFlags.Public | BindingFlags.Instance);

                var propertyMap = tResultProperties
                    .Join(tProperties,
                        srcProp => srcProp.Name,
                        destProp => destProp.Name,
                        (srcProp, destProp) => new { SourceProperty = srcProp, DestinationProperty = destProp })
                    .Where(pair => pair.SourceProperty.PropertyType.IsAssignableFrom(pair.DestinationProperty.PropertyType) ||
                                     pair.DestinationProperty.PropertyType.IsAssignableFrom(pair.SourceProperty.PropertyType))
                    .ToDictionary(pair => pair.SourceProperty.Name, pair => pair.DestinationProperty);

                T result = Activator.CreateInstance<T>();
                foreach (var prop in tResultProperties)
                {
                    var ps = tProperties.Where(p => p.Name == prop.Name);
                    if (ps != null && ps.Count() == 1 && propertyMap.TryGetValue(prop.Name, out PropertyInfo destProp))
                    {
                        try
                        {
                            destProp.SetValue(result, prop.GetValue(ext, null), null);
                        }
                        catch (Exception ex)
                        {
                            return default(T);
                        }
                    }
                }
                return result;
            }
            catch (Exception ex)
            {
                return default(T);
            }
        }
    }
}
